// libc includes
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <assert.h>

// my header
#include "expr.h"

//!! fix this function to use the other helper functions
pcg_expr_t *pcg_expr_create(int type, ...) {
	va_list ap;
	pcg_expr_t *e;
	
	e = malloc(sizeof(pcg_expr_t));
	if (e == NULL) return NULL;
	e->type = type;
	e->args = NULL;
	e->left = NULL;
	e->right = NULL;
	
	va_start(ap, type);
	switch (type) {
	case EXP_PLUS:
	case EXP_MINUS:
	case EXP_MULT:
	case EXP_DIV:
	case EXP_POW:
	case EXP_LT:
	case EXP_GT:
	case EXP_AND:
	case EXP_OR:
		e->left = va_arg(ap, pcg_expr_t*);
	case EXP_UMINUS:
	case EXP_NOT:
		e->right = va_arg(ap, pcg_expr_t*);
		break;
	case EXP_NUMBER:
		e->lval.dval = va_arg(ap, double);
		break;
	case EXP_FUNC:
		e->lval.sval = strdup(va_arg(ap, char*));
		e->args = va_arg(ap, pcg_exprlist_t*);
		break;
	case EXP_ID:
		e->lval.sval = strdup(va_arg(ap, char*));
		break;
	}
	
	return e;
}
		

pcg_expr_t *pcg_expr_create_number(double dval) {
	pcg_expr_t *e;
	e = malloc(sizeof(pcg_expr_t));
	if (e == NULL) return NULL;
	e->type = EXP_NUMBER;
	e->lval.dval = dval;
	e->args = NULL;
	e->left = NULL;
	e->right = NULL;
	return e;
}

pcg_expr_t *pcg_expr_create_id(char *sval) {
	return pcg_expr_create_func(sval, NULL);
}

pcg_expr_t *pcg_expr_create_binary(int type, pcg_expr_t *left, pcg_expr_t *right) {
	pcg_expr_t *e;
	e = malloc(sizeof(pcg_expr_t));
	if (e == NULL) return NULL;
	e->type = type;
	e->args = NULL;
	e->left = left;
	e->right = right;
	return e;
}

pcg_expr_t *pcg_expr_create_func(char *sval, pcg_exprlist_t *args) {
	pcg_expr_t *e;
	e = malloc(sizeof(pcg_expr_t));
	if (e == NULL) return NULL;
	if (args == NULL) e->type = EXP_ID;
	else e->type = EXP_FUNC;
	e->lval.sval = strdup(sval);
	if (e->lval.sval == NULL) {
		free(e);
		return NULL;
	}
	e->args = args;
	e->left = NULL;
	e->right = NULL;
	return e;
}

void pcg_expr_delete(pcg_expr_t *e) {
	assert(e);
	if (e->type == EXP_ID || e->type == EXP_FUNC) free(e->lval.sval);
	free(e);
}

void pcg_expr_destroy(pcg_expr_t *e) {
	if (e->left) pcg_expr_destroy(e->left);
	if (e->right) pcg_expr_destroy(e->right);
	//if (e->args) pcg_exprlist_destroy(e->args);
}

pcg_exprlist_t *pcg_exprlist_create(pcg_expr_t *e) {
	pcg_exprlist_t *el;
	el = malloc(sizeof(pcg_exprlist_t));
	if (el == NULL) return NULL;
	el->head = e;
	el->tail = NULL;
	return el;
}

void pcg_exprlist_delete(pcg_exprlist_t *el) {
	assert(el);
	free(el);
}

void pcg_exprlist_destroy(pcg_exprlist_t *el) {
	assert(el);
	if (el->tail != NULL) pcg_exprlist_destroy(el->tail);
	pcg_expr_destroy(el->head);
	free(el);
}

pcg_exprlist_t *pcg_exprlist_cons(pcg_exprlist_t *el, pcg_expr_t *e) {
	pcg_exprlist_t *nel;
	nel = pcg_exprlist_create(e);
	assert(nel);
	nel->tail = el;
	return nel;
}

int pcg_exprlist_length(pcg_exprlist_t *el) {
	int i = 0;
	
	while (el != NULL) {
		el = el->tail;
		i++;
	}
	
	return i;
}

// debug routines

void pcg_exprlist_print(pcg_exprlist_t *el) {
	pcg_expr_print(el->head);
	if (el->tail != NULL) {
		printf(", ");
		pcg_exprlist_print(el->tail);
	}
}

void pcg_expr_print(pcg_expr_t *e) {
	if (e == NULL) return;
	switch(e->type) {
	case EXP_NUMBER:
		printf("%f", e->lval.dval);
		break;
	case EXP_ID:
		printf("%s", e->lval.sval);
		break;
	case EXP_FUNC:
		printf("%s(", e->lval.sval);
		pcg_exprlist_print(e->args);
		printf(")");
		break;
	case EXP_UMINUS:
		printf("-(");
		pcg_expr_print(e->right);
		printf(")");
		break;
	case EXP_PLUS:
		printf("(");
		pcg_expr_print(e->left);
		printf(" + ");
		pcg_expr_print(e->right);
		printf(")");
		break;
	case EXP_MINUS:
		printf("(");
		pcg_expr_print(e->left);
		printf(" - ");
		pcg_expr_print(e->right);
		printf(")");
		break;
	case EXP_MULT:
		printf("(");
		pcg_expr_print(e->left);
		printf(" * ");
		pcg_expr_print(e->right);
		printf(")");
		break;
	case EXP_DIV:
		printf("(");
		pcg_expr_print(e->left);
		printf(" / ");
		pcg_expr_print(e->right);
		printf(")");
		break;
	case EXP_POW:
		printf("(");
		pcg_expr_print(e->left);
		printf(" ^ ");
		pcg_expr_print(e->right);
		printf(")");
		break;
	}
		
}
